<?php
declare (strict_types=1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;

class MovePics extends Command
{
    private $exifInfo = [];

    protected function configure()
    {
        // 指令配置
        $this->setName('movepics')
            ->addArgument('source', Argument::REQUIRED, 'Please type the absolute path to store the source image')//输入源文件路径
            ->addArgument('output', Argument::OPTIONAL, 'Please type the absolute path for the picture output,default:to source path')//输出,默认同源文件路径
            ->addOption('dirmode', 'dm', Option::VALUE_OPTIONAL, 'devicemodel:Device/Model,datetime:Y/m/d,both(default):Device/Model/Y/m/d', 'both')//目录名格式
            ->addOption('filenamemode', 'fm', Option::VALUE_OPTIONAL, 'original:Original filename,datetime(default):Y/m/d H:i:s', 'datetime')//文件名格式,默认日期时间
            ->addOption('extendnamemode', 'em', Option::VALUE_OPTIONAL, 'original:Original filename,mime:mime file type,exif(default):exif file type', 'exif')//扩展名模式,默认exif读取,exif读取失败使用源扩展名
            ->addOption('mode', 'mm', Option::VALUE_OPTIONAL, "Run mode,move or copy", 'move')//强制模式,替换已有文件
            ->addOption('force', 'f', Option::VALUE_OPTIONAL, "Force mode,replace exist file", true)//强制模式,替换已有文件
            ->addOption('timeout', 'time', Option::VALUE_OPTIONAL, 'timeout:number,default 0', 0)//闲置运行时间
            ->setDescription('Move all your pictures to the designated location, and create a new directory according to the device name or shooting time');
    }

    protected function execute(Input $input, Output $output)
    {
        set_time_limit($input->hasOption('timeout') ? abs((int)$input->getOption('timeout')) : 0); //限制时间,选项值取正取整,防止输入负数或其它字符
        $sourceDir = $this->trimDir($input->getArgument('source'));
        if (!is_dir($sourceDir)) {
            $output->writeln($sourceDir . " not dir,application abort");
            die();
        }
        if ($input->hasArgument('output')) {
            $outputDir = $this->trimDir($input->getArgument('output'));
        } else {
            $outputDir = $sourceDir;
        }
        $this->move($input, $output, $sourceDir, $outputDir);
        $output->writeln("Move pictures done,application finnish");
        die();
    }

    /**
     * 处理路径,去除空格,加/
     * @param $dir string 要处理的路径名,末尾没有/ 加上 / ,如果是\ 替换成 /
     * @return $dir string 处理完的路径名
     */


    private function trimDir($dir)
    {
        if (!empty($dir)) {
            $dir = trim($dir);
        }
        $dirSymbol = substr($dir, strlen($dir) - 1, 1);
        if (!in_array($dirSymbol, ['/', '\\', DIRECTORY_SEPARATOR])) {
            $dir .= DIRECTORY_SEPARATOR;
        } else {
            $dir = substr($dir, 0, strlen($dir) - 1) . DIRECTORY_SEPARATOR;
        }
        return $dir;
    }

    /**
     * 移动图片图片文件
     * @param $input Input 输入
     * @param $output Output 输出对象
     * @param $sourceDir string 源路径
     * @param $outputDir string 输出路径
     * @return mixed 完成
     */
    private function move($input, $output, $sourceDir, $outputDir)
    {
        $this->exifInfo = [];
        $isForce = false;
        $sourceDir = $this->trimDir($sourceDir);
        $handle = opendir($sourceDir);
        while (($filename = readdir($handle)) !== false) {
            if ($filename == '.' || $filename == '..') {
                $output->writeln("{$filename} is not image file, skip");
                continue;
            }
            $file = $sourceDir . $filename;
            if (is_file($file)) {
                if (!$this->isImage($file)) {
                    $output->writeln("{$file} is not image file, skip");
                    continue;
                }
                $newDirname = $this->getNewDirname($file, $input->getOption('dirmode'), $outputDir);
                $newFilename = $this->getNewFilename($file, $input->getOption('filenamemode'), $filename);
                $extendName = $this->getExtName($file, $input->getOption('extendnamemode'));
                if (!is_dir($newDirname)) {
                    if (false === $this->mkDirs($newDirname)) {
                        $output->writeln("mkdir {$newDirname} error , skip");
                        continue;
                    }
                }
//                如果强制模式,删除原有文件
                $fullPathName = $newDirname . $newFilename . $extendName;
                if (file_exists($fullPathName)) {
                    if ($input->hasOption("force")) {
                        $isForce = $input->getOption("force");
                    }
                    if ($isForce) {
                        @copy($fullPathName, $fullPathName . ".bak");
                    } else {
                        $output->writeln("not has force option,{$fullPathName} exists,skip");
                        continue;
                    }
                }
                $mode = $input->getOption('mode');
                $operatorSymbol = $mode == 'move' ? "rename" : "copy";
                if (false === @$operatorSymbol($file, $fullPathName)) {
                    $output->writeln("{$mode} {$file} to {$fullPathName} error,skip");
                } else {
                    $output->writeln("{$mode} {$file} to {$fullPathName} success,next");
                    if ($isForce) {
                        if (@unlink($fullPathName . ".bak")) {
                            $output->writeln("has force option,{$fullPathName} exists,delete success");
                        } else {
                            $output->writeln("has force option,{$fullPathName} exists,delete error");
                        }
                    }
                }
            } elseif (is_dir($file)) {
                $this->move($input, $output, $file, $outputDir);
            }
        }
    }

    /**
     * 判断是否图片文件
     * @param $file string 要判断的源文件,绝对路径
     * @return  true|false bool 是否图片文件
     */

    private function isImage($file)
    {
//        有文档说5.3以后 已取消mime_content_type函数
        if (function_exists("mime_content_type")) {
            $mime = mime_content_type($file);
            list($fileType, $extName) = explode("/", $mime);
            if ("image" == strtolower($fileType)) {
                return true;
            } else {
                return false;
            }
        }
        if (function_exists("exif_imagetype")) {
//            获取文件类型常量 返回值示例:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
//            IMAGETYPE_BMP=6; IMAGETYPE_JPEG=2  IMAGETYPE_UNKNOW=0
//            IMAGETYPE_COUNT=19 2021/05/13 图片文件类型总个数
            $exifImageType = exif_imagetype($file);//如果不是图片 就返回 false 而未知图片类型是0 所以判断返回值应该用false===$exifImageType,不能用==
            if ($exifImageType > 0 && $exifImageType < IMAGETYPE_COUNT) {
                return true;
            } else {
                return false;
            }
        }
//            通过finfo获取,因为thinkphp6不开启开启fileinfo扩展会有很多问题,所以默认此函数是可用的
        $fileInfo = finfo_open(FILEINFO_MIME);
        $mimeType = finfo_file($fileInfo, $file); //返回值示例:$mimeType="application/octet-stream; charset=binary";
        finfo_close($fileInfo);
//          截取第一个/前面的字符
//          strpos 查找字符串出现的位置
//          haystack 柴火垛 needle 一根火柴
//        如果查找不到/ 或者 是首个字符(返回0),说明程序有问题,应返回错误
        return strpos($mimeType, "/") ? "image" == substr($mimeType, 0, strpos($mimeType, "/")) : false;
    }


    /**
     * 生成新路径
     * @param $file string 生成新路径的源文件,绝对路径
     * @param $dirmode string original|datetime 生成路径的方式 devicemodel设备名称型号 datetime 日期时间 both二者都有 默认 both
     * @return  $newDirname string 新路径名,带DIRECTORY_SEPARATOR
     */

    private function getNewDirname($file, $dirmode = 'both', $outputDir)
    {
        $newDirname = $outputDir;
        try {
            $this->exifInfo = empty($this->exifInfo) ? exif_read_data($file) : $this->exifInfo;
            $devicemodel = '';
            if (key_exists("Make", $this->exifInfo) && !empty($this->exifInfo['Make'])) {
                $devicemodel .= trim($this->exifInfo['Make']) . DIRECTORY_SEPARATOR;
            } else {
                $devicemodel .= "Other Brand" . DIRECTORY_SEPARATOR;
            }
            if (key_exists("Model", $this->exifInfo) && !empty($this->exifInfo['Model'])) {
                $devicemodel .= trim($this->exifInfo['Model']) . DIRECTORY_SEPARATOR;
            } else {
                $devicemodel .= "Other Model" . DIRECTORY_SEPARATOR;
            }
        } catch (\Exception $e) {
            $devicemodel = "Other Brand" . DIRECTORY_SEPARATOR . "Other Model" . DIRECTORY_SEPARATOR;
        }
        try {
            $this->exifInfo = empty($this->exifInfo) ? exif_read_data($file) : $this->exifInfo;
            if (key_exists('DateTimeOriginal', $this->exifInfo) && !empty($this->exifInfo['DateTimeOriginal'])) {
                $datetime = date("Y/m", strtotime($this->exifInfo['DateTimeOriginal'])) . DIRECTORY_SEPARATOR;
            } elseif (key_exists('DateTime', $this->exifInfo) && !empty($this->exifInfo['DateTime'])) {
                $datetime = date("Y/m", strtotime($this->exifInfo['DateTime'])) . DIRECTORY_SEPARATOR;
            } elseif (key_exists('FileDateTime', $this->exifInfo) && !empty($this->exifInfo['FileDateTime'])) {
                $datetime = date("Y/m", $this->exifInfo['FileDateTime']) . DIRECTORY_SEPARATOR;
            } else {
                $datetime = date("Y/m") . DIRECTORY_SEPARATOR;
            }
        } catch (\Exception $e) {
            $datetime = date("Y/m") . DIRECTORY_SEPARATOR;
        }
        switch ($dirmode) {
            case 'devicemodel':
                $newDirname .= $devicemodel;
                break;
            case 'datetime':
                $newDirname .= $datetime;
                break;
            case 'both':
                $newDirname .= $devicemodel . $datetime;
                break;
        }
        return $newDirname;
    }

    /**
     * 生成新的文件名
     * @param $file string 生成新文件名的源文件,绝对路径
     * @param $filenamemode string original|datetime 生成文件名的方式 original原始文件名 datetime 日期时间 默认 datetime
     * @return  $newFilename string 新文件名
     */

    private function getNewFilename($file, $filenamemode = 'datetime', $oldFilename)
    {
        $newFilename = '';
        switch ($filenamemode) {
            case 'original':
                $newFilename = pathinfo($oldFilename, PATHINFO_FILENAME);
                break;
            case 'datetime':
                try {
                    $this->exifInfo = empty($this->exifInfo) ? exif_read_data($file) : $this->exifInfo;
                    if (key_exists('DateTimeOriginal', $this->exifInfo) && !empty($this->exifInfo['DateTimeOriginal'])) {
                        $newFilename = date("Y-m-d H:i:s", strtotime($this->exifInfo['DateTimeOriginal']));
                    } elseif (key_exists('DateTime', $this->exifInfo) && !empty($this->exifInfo['DateTime'])) {
                        $newFilename = date("Y-m-d H:i:s", strtotime($this->exifInfo['DateTime']));
                    } elseif (key_exists('FileDateTime', $this->exifInfo) && !empty($this->exifInfo['FileDateTime'])) {
                        $newFilename = date("Y-m-d H:i:s", $this->exifInfo['FileDateTime']);
                    } else {
                        $newFilename = date("Y-m-d H:i:s");
                    }
                } catch (\Exception $e) {
                    $newFilename = date("Y-m-d H:i:s");
                }
                break;
        }
        return $newFilename ?: date("Y-m-d H:i:s");
    }


    /**
     * 获取扩展名
     * @param $file string 获取扩展名的源文件,全部路径
     * @param $action string original|exif|mime 获取扩展名的方式 默认 exif
     * @return  $extName string 获取的扩展名
     */
    private function getExtName($file, $action = 'exif')
    {
        $extName = false;
        switch ($action) {
            case 'original':
                $extName = "." . pathinfo($file, PATHINFO_EXTENSION);
                break;
            case 'exif':
                try {
                    $this->exifInfo = empty($this->exifInfo) ? exif_read_data($file) : $this->exifInfo;
                    $extName = image_type_to_extension($this->exifInfo['FileType'], true);
                } catch (\Exception $e) {
                    return $this->getExtName($file, 'mime');
                }
                break;
            case 'mime':
                try {
                    $info = @getimagesize($file);
                    $extName = image_type_to_extension($info['2'], true);
                } catch (\Exception $e) {
                    return $this->getExtName($file, 'original');
                }
                break;
        }
        return $extName;
    }

    /**
     * 递归创建目录
     * @param $dir string 要递归创建的目录
     * @return true|false bool 创建成功失败
     */
    private function mkDirs($dir)
    {
        if (!is_dir($dir)) {
            if (!$this->mkDirs(dirname($dir))) {
                return false;
            }
            if (!mkdir($dir, 0777)) {
                return false;
            }
        }
        return true;
    }
}
